package jmine.tec.utils.date;

import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectOutput;
import java.util.Calendar;

import javax.persistence.Embeddable;
import javax.persistence.Transient;

/**
 * Represents a Date without all the fields set. Embeddable to enable use in any JPA context.
 * 
 * @author takeshi
 */
@Embeddable
public class PartialDate implements Externalizable, Comparable<PartialDate> {
    /**
     * 
     */
    private static final long serialVersionUID = -7336602945406755932L;

    private int dayOfMonth = -1, month = -1, year = -1;

    /**
     * @param year int
     * @param month int
     * @param dayOfMonth int
     */
    public PartialDate(final int year, final int month, final int dayOfMonth) {
        super();
        this.year = year;
        this.month = month;
        this.dayOfMonth = dayOfMonth;
    }

    /**
     * C'tor that skips month and day (in other words, servs only as an year).
     * <p>
     * Equivalent to a call to new PartialDate(year, {@link PartialDate#BLANK_FIELD} , {@link PartialDate#BLANK_FIELD}).
     * 
     * @param year the year
     * @see #PartialDate(int, int, int)
     */
    public PartialDate(final int year) {
        this(year, BLANK_FIELD, BLANK_FIELD);
    }

    /**
     * C'tor that skips the day - this instance will hold the year and the month.
     * <p>
     * Equivalent to a call to new PartialDate(year, month , {@link PartialDate#BLANK_FIELD}).]
     * 
     * @param year the year
     * @param month the month, as used by {@link Calendar}
     * @see #PartialDate(int, int, int)
     */
    public PartialDate(final int year, final int month) {
        this(year, month, BLANK_FIELD);
    }

    /**
     * Default C'tor
     */
    public PartialDate() {
    }

    /**
     * @param dayOfMonth the dayOfMonth to set
     */
    protected void setDayOfMonth(final int dayOfMonth) {
        this.dayOfMonth = dayOfMonth;
    }

    /**
     * @param month the month to set
     */
    protected void setMonth(final int month) {
        this.month = month;
    }

    /**
     * @param year the year to set
     */
    protected void setYear(final int year) {
        this.year = year;
    }

    /**
     * @return the dayOfMonth
     */
    public int getDayOfMonth() {
        return this.dayOfMonth;
    }

    /**
     * @return the month
     */
    public int getMonth() {
        return this.month;
    }

    /**
     * @return the year
     */
    public int getYear() {
        return this.year;
    }

    /**
     * @param in the {@link ObjectInput}
     * @throws IOException in IOException
     */
    public void readExternal(final ObjectInput in) throws IOException {
        this.year = in.readInt();
        this.month = in.readInt();
        this.dayOfMonth = in.readInt();
    }

    /**
     * @param out the {@link ObjectOutput}
     * @throws IOException e
     */
    public void writeExternal(final ObjectOutput out) throws IOException {
        out.writeInt(this.year);
        out.writeInt(this.month);
        out.writeInt(this.dayOfMonth);
    }

    /**
     * @param o the {@link PartialDate}
     * @return int
     */
    public int compareTo(final PartialDate o) {
        if (this.year != o.year) {
            return this.year - o.year;
        }
        if (this.month != o.month) {
            return this.month - o.month;
        }
        if (this.dayOfMonth != o.dayOfMonth) {
            return this.dayOfMonth - o.dayOfMonth;
        }
        return 0;
    }

    /**
     * @return int
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + this.dayOfMonth;
        result = prime * result + this.month;
        result = prime * result + this.year;
        return result;
    }

    /**
     * (non-Javadoc)
     * 
     * @param obj {@link Object} to compare
     * @return boolean
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(final Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (this.getClass() != obj.getClass()) {
            return false;
        }
        final PartialDate other = (PartialDate) obj;
        if (this.dayOfMonth != other.dayOfMonth) {
            return false;
        }
        if (this.month != other.month) {
            return false;
        }
        if (this.year != other.year) {
            return false;
        }
        return true;
    }

    /**
     * @return boolean
     */
    @Transient
    public boolean isDaySet() {
        return this.dayOfMonth != BLANK_FIELD;
    }

    /**
     * @return boolean
     */
    @Transient
    public boolean isMonthSet() {
        return this.month != BLANK_FIELD;
    }

    /**
     * @return boolean
     */
    @Transient
    public boolean isYearSet() {
        return this.month != BLANK_FIELD;
    }

    /**
     * Sets the initialized fields of this {@link PartialDate} into the given calendar, if any.
     * 
     * @param calendar the {@link Calendar}
     * @return modified {@link Calendar}
     */
    public Calendar setFields(final Calendar calendar) {
        if (this.isDaySet()) {
            calendar.set(Calendar.DAY_OF_MONTH, this.dayOfMonth);
        }
        if (this.isMonthSet()) {
            calendar.set(Calendar.MONTH, this.month);
        }
        if (this.isYearSet()) {
            calendar.set(Calendar.YEAR, this.year);
        }
        return calendar;
    }

    /**
     * @return {@link String}
     */
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        this.appendIfSet(sb, this.year);
        sb.append("/");
        this.appendIfSet(sb, this.month);
        sb.append("/");
        this.appendIfSet(sb, this.dayOfMonth);
        return sb.toString();
    }

    /**
     * @param sb {@link StringBuilder}
     * @param field int
     */
    private void appendIfSet(final StringBuilder sb, final int field) {
        if (field != BLANK_FIELD) {
            sb.append(field);
        } else {
            sb.append("____");
        }
    }

    /**
     * Used to indicate fields not set.
     */
    public static final int BLANK_FIELD = -1;

    /**
     * Value of the {@link #MONTH} field indicating the first month of the year.
     */
    public static final int JANUARY = 0;

    /**
     * Value of the {@link #MONTH} field indicating the second month of the year.
     */
    public static final int FEBRUARY = 1;

    /**
     * Value of the {@link #MONTH} field indicating the third month of the year.
     */
    public static final int MARCH = 2;

    /**
     * Value of the {@link #MONTH} field indicating the fourth month of the year.
     */
    public static final int APRIL = 3;

    /**
     * Value of the {@link #MONTH} field indicating the fifth month of the year.
     */
    public static final int MAY = 4;

    /**
     * Value of the {@link #MONTH} field indicating the sixth month of the year.
     */
    public static final int JUNE = 5;

    /**
     * Value of the {@link #MONTH} field indicating the seventh month of the year.
     */
    public static final int JULY = 6;

    /**
     * Value of the {@link #MONTH} field indicating the eighth month of the year.
     */
    public static final int AUGUST = 7;

    /**
     * Value of the {@link #MONTH} field indicating the ninth month of the year.
     */
    public static final int SEPTEMBER = 8;

    /**
     * Value of the {@link #MONTH} field indicating the tenth month of the year.
     */
    public static final int OCTOBER = 9;

    /**
     * Value of the {@link #MONTH} field indicating the eleventh month of the year.
     */
    public static final int NOVEMBER = 10;

    /**
     * Value of the {@link #MONTH} field indicating the twelfth month of the year.
     */
    public static final int DECEMBER = 11;

}
